package quake

import (
	"bufio"
	"bytes"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"github.com/gookit/color"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
)

type Config struct {
	Email          string
	Hunter_token   string
	Quake_token    string
	Quake_timeout  string
	Hunter_timeout string
	Fofa_token     string
	Fofa_timeout   string
	Maxpage        string
}

type QuakeData struct {
	Query string `json:"query"`
	Start int    `json:"start"`
	Size  int    `json:"size"`
}

type AutoGenerated struct {
	Code    int    `json:"code"`
	Message string `json:"message"`
	Data    []struct {
		Hostname string `json:"hostname"`
		Org      string `json:"org"`
		Port     int    `json:"port"`
		Service  struct {
			Response     string `json:"response"`
			ResponseHash string `json:"response_hash"`
			Name         string `json:"name"`
			Version      string `json:"version"`
			Socks5       struct {
				AuthAcceptedMethod string `json:"auth_accepted_method"`
				Version            int    `json:"version"`
			} `json:"socks5"`
		} `json:"service"`
		Ip        string `json:"ip"`
		OsVersion string `json:"os_version"`
		OsName    string `json:"os_name"`
		Location  struct {
			Owner       string    `json:"owner"`
			ProvinceCn  string    `json:"province_cn"`
			ProvinceEn  string    `json:"province_en"`
			Isp         string    `json:"isp"`
			CountryEn   string    `json:"country_en"`
			DistrictCn  string    `json:"district_cn"`
			Gps         []float64 `json:"gps"`
			StreetCn    string    `json:"street_cn"`
			CityEn      string    `json:"city_en"`
			DistrictEn  string    `json:"district_en"`
			CountryCn   string    `json:"country_cn"`
			StreetEn    string    `json:"street_en"`
			CityCn      string    `json:"city_cn"`
			CountryCode string    `json:"country_code"`
			Asname      string    `json:"asname"`
			SceneCn     string    `json:"scene_cn"`
			SceneEn     string    `json:"scene_en"`
			Radius      int       `json:"radius"`
		} `json:"location"`
		IsIpv6    bool      `json:"is_ipv6"`
		Transport string    `json:"transport"`
		Time      time.Time `json:"time"`
		Asn       int       `json:"asn"`
	} `json:"data"`
	Meta struct {
		Pagination struct {
			Count     int `json:"count"`
			PageIndex int `json:"page_index"`
			PageSize  int `json:"page_size"`
			Total     int `json:"total"`
		} `json:"pagination"`
	} `json:"meta"`
}

// 获取当前执行程序所在的绝对路径
func GetCurrentAbPathByExecutable() string {
	exePath, err := os.Executable()
	if err != nil {
		log.Fatal(err)
	}
	res, _ := filepath.EvalSymlinks(filepath.Dir(exePath))
	return res
}

func GetConfig() Config {
	//创建一个空的结构体,将本地文件读取的信息放入
	c := &Config{}
	//创建一个结构体变量的反射
	cr := reflect.ValueOf(c).Elem()
	//打开文件io流
	optSys := runtime.GOOS
	path := ""
	if optSys == "windows" {
		path = GetCurrentAbPathByExecutable() + "\\config.ini"
	} else {
		path = GetCurrentAbPathByExecutable() + "/config.ini"
	}
	f, err := os.Open(path)
	if err != nil {
		//log.Fatal(err)
		color.RGBStyleFromString("244,211,49").Println("[Error] Quake configuration file error!!!")
		os.Exit(1)
	}
	defer func() {
		if err = f.Close(); err != nil {
			log.Fatal(err)
		}
	}()
	//我们要逐行读取文件内容
	s := bufio.NewScanner(f)
	for s.Scan() {
		//以=分割,前面为key,后面为value
		var str = s.Text()
		var index = strings.Index(str, "=")
		var key = str[0:index]
		var value = str[index+1:]
		//通过反射将字段设置进去
		cr.FieldByName(key).Set(reflect.ValueOf(value))
	}
	err = s.Err()
	if err != nil {
		log.Fatal(err)
	}
	//返回Config结构体变量
	return *c
}

func quake_api(keyword string, start int, size int) QuakeData {
	reqData := QuakeData{}
	reqData.Query = keyword
	reqData.Start = start
	reqData.Size = size

	return reqData
}

func quakehttp(postdata QuakeData, key string, timeout string) *AutoGenerated {
	var itime, err = strconv.Atoi(timeout)
	if err != nil {
		log.Println("quake超时参数错误: ", err)
	}
	transport := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
	client := &http.Client{
		Timeout:   time.Duration(itime) * time.Second,
		Transport: transport,
	}
	url := "https://quake.360.cn/api/v3/search/quake_service"
	payload, _ := json.Marshal(postdata)
	req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload))
	if err != nil {
		log.Fatal(err)
	}
	req.Header.Set("content-type", "application/json")
	req.Header.Set("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36")
	req.Header.Set("X-QuakeToken", key)
	resp, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()
	result, _ := io.ReadAll(resp.Body)
	res := &AutoGenerated{}
	json.Unmarshal(result, &res)
	return res
}

func Quakeall(keyword string) (urls []string) {
	color.RGBStyleFromString("244,211,49").Println("请耐心等待quake搜索......")
	quake := GetConfig()
	postdata := quake_api(keyword, 0, 500)
	res := quakehttp(postdata, quake.Quake_token, quake.Quake_timeout)
	pagelength := res.Meta.Pagination.Total/500 + 1
	for _, value := range res.Data {
		urls = append(urls, value.Ip+":"+strconv.Itoa(value.Port))
	}
	Maxpage, _ := strconv.Atoi(quake.Maxpage)
	if pagelength > 1 {
		for i := 1; i <= pagelength && i <= Maxpage*2; i++ {
			fmt.Println("正在请求第" + strconv.Itoa(i) + "页数据")
			postdata = quake_api(keyword, i*500, 500)
			res = quakehttp(postdata, quake.Quake_token, quake.Quake_timeout)
			if len(res.Data) > 0 {
				for _, value := range res.Data {
					urls = append(urls, value.Ip+":"+strconv.Itoa(value.Port))
				}
			} else {
				break
			}
		}
	}

	return urls
}
